package qq2564874169.tuaotuao.fx.app;

import jakarta.servlet.http.HttpServletRequest;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationContextFactory;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import qq2564874169.tuaotuao.fx.FxEnv;
import qq2564874169.tuaotuao.fx.context.BeanContext;

import java.util.UUID;

public class FxSpringApp extends SpringBootServletInitializer {
    protected boolean isContextWithRequest;
    private SpringApplication app;
    private WebApplicationContext applicationContext;
    private MvcEvents mvcEvents;
    private String requestContextKey = UUID.randomUUID().toString();

    public Class<?> getMainClass() {
        return app.getMainApplicationClass();
    }

    public MvcEvents getMvcEvents() {
        return this.mvcEvents;
    }

    protected MvcEvents createMvcEvents() {
        return new MvcEvents();
    }

    public void run(String... args) {
        run(getClass(), args);
    }

    public void run(Class<?> mainClass, String... args) {
        if (FxEnv.getMainClass() == null) {
            FxEnv.setMainClass(mainClass);
        }
        mvcEvents = createMvcEvents();
        app = createSpringApplication(mainClass);
        app.run(args);
    }

    private void settingApplicationContext(WebApplicationContext context) {
        applicationContext = context;
        context.registerBean((Class<? super FxSpringApp>) getClass(), () -> this, def -> def.setPrimary(true));
        context.registerBean(MvcEvents.class, () -> this.mvcEvents);
        context.registerBean(DefaultCallback.class);
//        context.onRegisterBean.add((s, e) -> {
//            if (e.define.isSingleton()) {
//                Class<?> clazz = ClassUtils.getClassForName(e.define.getBeanClassName());
//                e.isCancel = clazz != null && SpringBootServletInitializer.class.isAssignableFrom(clazz);
//            }
//        });
        initApplicationContext(context);
    }

    protected Integer getPort() {
        return null;
    }

    protected Integer getMaxPostSize() {
        return null;
    }

    protected void initApplicationContext(WebApplicationContext context) {

    }

    protected void setWebServer(ConfigurableServletWebServerFactory factory) {

    }

    protected void appStartup(WebApplicationContext context, ApplicationArguments args) {

    }

    private void settingStartup(WebApplicationContext context, ApplicationArguments args) {
        registerRequestContext();
        registerControllerEvents();
        appStartup(context, args);
    }

    public ApplicationContext getApplicationContext(HttpServletRequest request) {
        if (isContextWithRequest) {
            var ctx = (ApplicationContext) request.getAttribute(requestContextKey);
            if (ctx == null) {
                ctx = createRequestApplicationContext(applicationContext);
                request.setAttribute(requestContextKey, ctx);
            }
            return ctx;
        } else {
            return applicationContext;
        }
    }

    protected ApplicationContext createRequestApplicationContext(WebApplicationContext context) {
        return (ApplicationContext) context.child();
    }

    private void registerRequestContext() {
        mvcEvents.evHandlerBefore.add((t, e) -> {
            if (isContextWithRequest) {
                var ctx = getApplicationContext(e.request);
                e.handler = ctx.getBean(e.type);
                e.request.setAttribute(requestContextKey, ctx);
            }
        });
        mvcEvents.evRequestAfter.add((t, e) -> {
            if (isContextWithRequest) {
                Object value = e.request.getAttribute(requestContextKey);
                if (value instanceof BeanContext ctx) {
                    ctx.close();
                }
            }
        });
    }

    private void registerControllerEvents() {
        mvcEvents.evActionBefore.add((t, e) -> {
            if (e.handler instanceof FxController controller) {
                controller.onActionBefore(e);
            }
        });
        mvcEvents.evActionAfter.add((t, e) -> {
            if (e.handler instanceof FxController controller) {
                controller.onActionAfter(e);
            }
        });
        mvcEvents.evActionError.add((t, e) -> {
            if (e.handler instanceof FxController controller) {
                controller.onActionError(e);
            }
        });
    }

    protected SpringApplication createSpringApplication(Class<?> mainClass) {
        if (app == null) {
            app = new SpringApplication(mainClass) {
                @Override
                protected ConfigurableApplicationContext createApplicationContext() {
                    if (applicationContext == null) {
                        applicationContext = new WebApplicationContext();
                        settingApplicationContext(applicationContext);
                    }
                    return applicationContext;
                }
            };
        }
        return app;
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        builder.contextFactory(ApplicationContextFactory.ofContextClass(WebApplicationContext.class));
        builder.initializers(context -> settingApplicationContext((WebApplicationContext) context));
        return builder.sources(getClass());
    }

    private class DefaultCallback implements ApplicationRunner, WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
        @Override
        public void run(ApplicationArguments args) {
            settingStartup(applicationContext, args);
        }

        @Override
        public void customize(ConfigurableServletWebServerFactory factory) {
            TomcatServletWebServerFactory tomcat = (TomcatServletWebServerFactory) factory;
            if (getPort() != null) {
                tomcat.setPort(getPort());
            }
            if (getMaxPostSize() != null) {
                tomcat.addConnectorCustomizers(conn -> {
                    conn.setMaxPostSize(getMaxPostSize());
                });
            }
            setWebServer(factory);
        }
    }
}
